export class MindMap {
    constructor({ canvas, input, data }) {
        this.canvas = canvas;
        this.ctx = canvas.getContext('2d');
        this.input = input;
        this.root = data;
        this.nodeId = 100;

        this.draggingNode = null;
        this.offsetX = 0;
        this.offsetY = 0;

        this.viewOffset = { x: 0, y: 0 };
        this.viewDragging = false;
        this.viewDragStart = { x: 0, y: 0 };

        this.scale = 1;

        this.activeNode = null;

        this.tagInput = document.createElement('input');
        this.tagInput.style.position = 'absolute';
        this.tagInput.style.display = 'none';
        document.body.appendChild(this.tagInput);

        const dpr = window.devicePixelRatio || 1;
        const rect = canvas.getBoundingClientRect();
        canvas.width = rect.width * dpr;
        canvas.height = rect.height * dpr;
        this.ctx.scale(dpr, dpr);

        this._initializeCollapsed(this.root);

        this._bindEvents();
    }

    _initializeCollapsed(node) {
        node.collapsed = false;
        if (node.children) {
            node.children.forEach(child => this._initializeCollapsed(child));
        }
    }

    _createContextMenu() {
        const menu = document.createElement('div');
        menu.id = 'contextMenu';
        menu.style.position = 'absolute';
        menu.style.display = 'none';
        menu.style.background = '#fff';
        menu.style.border = '1px solid #ccc';
        menu.style.padding = '5px';
        menu.style.zIndex = '1000';
        menu.innerHTML = `
            <div id="addChild" style="cursor: pointer; padding: 5px;">Add Child</div>
            <div id="addSibling" style="cursor: pointer; padding: 5px;">Add Sibling</div>
        `;
        document.body.appendChild(menu);
        return menu;
    }

    _bindEvents() {
        this.canvas.oncontextmenu = e => {
            e.preventDefault();
            const { x, y } = this._getMouse(e);
            const node = this._getNodeAt(x, y);

            if (node) {
                let contextMenu = document.getElementById('contextMenu');
                if (!contextMenu) {
                    console.warn('Context menu not found. Creating dynamically.');
                    contextMenu = this._createContextMenu();
                }
                const addChild = document.getElementById('addChild');
                const addSibling = document.getElementById('addSibling');

                contextMenu.style.left = `${e.clientX}px`;
                contextMenu.style.top = `${e.clientY}px`;
                contextMenu.style.display = 'block';

                const handleAddChild = () => {
                    const newNode = { id: this.nodeId++, text: '新节点', children: [], tags: [], collapsed: false };
                    node.children.push(newNode);
                    this.render();
                    contextMenu.style.display = 'none';
                };

                const handleAddSibling = () => {
                    const parent = this._findParent(this.root, node);
                    if (parent) {
                        const newNode = { id: this.nodeId++, text: '新节点', children: [], tags: [], collapsed: false };
                        parent.children.push(newNode);
                        this.render();
                    }
                    contextMenu.style.display = 'none';
                };

                addChild.onclick = null;
                addSibling.onclick = null;

                addChild.onclick = handleAddChild;
                addSibling.onclick = handleAddSibling;

                const closeMenu = () => {
                    contextMenu.style.display = 'none';
                    document.removeEventListener('click', closeMenu);
                };
                document.addEventListener('click', closeMenu);
            }
            return false;
        };

        this.canvas.addEventListener('mousedown', e => {
            this.input.style.display = 'none';
            this.tagInput.style.display = 'none';
            const { x, y } = this._getMouse(e);
            const node = this._getNodeAt(x, y);

            if (e.button === 0 && node) {
                if (node.children.length > 0) {
                    const buttonX = node.x + node.width / 2 + 15;
                    const buttonY = node.y;
                    const buttonRadius = 10;
                    const dx = x - buttonX;
                    const dy = y - buttonY;
                    if (dx * dx + dy * dy <= buttonRadius * buttonRadius) {
                        node.collapsed = !node.collapsed;
                        this.render();
                        return;
                    }
                }

                this.draggingNode = node;
                this.offsetX = x - node.x;
                this.offsetY = y - node.y;
                this.activeNode = node;
                this._drawOnly();
            }

            if (e.button === 0 && !node) {
                this.viewDragging = true;
                this.viewDragStart = { x: e.clientX, y: e.clientY };
            }
        });

        this.canvas.addEventListener('mousemove', e => {
            if (this.draggingNode) {
                const { x, y } = this._getMouse(e);
                this.draggingNode.x = x - this.offsetX;
                this.draggingNode.y = y - this.offsetY;
                this._drawOnly();
            }

            if (this.viewDragging) {
                const dx = e.clientX - this.viewDragStart.x;
                const dy = e.clientY - this.viewDragStart.y;
                this.viewOffset.x += dx;
                this.viewOffset.y += dy;
                this.viewDragStart = { x: e.clientX, y: e.clientY };
                this._drawOnly();
            }
        });

        this.canvas.addEventListener('mouseup', () => {
            if (this.draggingNode) {
                this._layoutTree(this.root);
                this.render();
            }
            this.draggingNode = null;
            this.viewDragging = false;
        });

        this.canvas.addEventListener('dblclick', e => {
            const { x, y } = this._getMouse(e);
            const node = this._getNodeAt(x, y);
            if (node) {
                const buttonOffset = node.children.length > 0 ? 30 : 0;
                const tagOffset = 10;
                const isTagArea = node.tags && node.tags.length > 0 && x > node.x + node.width / 2 + buttonOffset;

                if (isTagArea) {
                    this.tagInput.value = node.tags ? node.tags.join(' ') : '';

                    const rect = this.canvas.getBoundingClientRect();
                    const dpr = window.devicePixelRatio || 1;
                    const screenX = ((node.x + node.width / 2 + buttonOffset + tagOffset) * this.scale + this.viewOffset.x) / dpr + rect.left;
                    const screenY = (node.y * this.scale + this.viewOffset.y) / dpr + rect.top;

                    this.tagInput.style.left = `${screenX}px`;
                    this.tagInput.style.top = `${screenY - 12}px`;
                    this.tagInput.style.width = '100px';
                    this.tagInput.style.height = '24px';
                    this.tagInput.style.fontSize = '12px';
                    this.tagInput.style.display = 'block';
                    this.tagInput.style.zIndex = '1000';
                    this.tagInput.style.backgroundColor = 'white';
                    this.tagInput.style.border = '1px solid #ccc';
                    this.tagInput.style.borderRadius = '3px';
                    this.tagInput.style.padding = '0 5px';

                    this.tagInput.focus();

                    const finishTagEdit = () => {
                        const tags = this.tagInput.value.trim().split(/\s+/).filter(tag => tag);
                        node.tags = tags;
                        this.tagInput.style.display = 'none';
                        this.tagInput.removeEventListener('blur', finishTagEdit);
                        this.tagInput.removeEventListener('keydown', handleTagKeydown);
                        this.render();
                    };

                    const handleTagKeydown = e => {
                        if (e.key === 'Enter') this.tagInput.blur();
                    };

                    this.tagInput.addEventListener('blur', finishTagEdit);
                    this.tagInput.addEventListener('keydown', handleTagKeydown);
                } else {
                    this.input.value = node.text;
                    const rect = this.canvas.getBoundingClientRect();
                    const dpr = window.devicePixelRatio || 1;
                    const screenX = (node.x * this.scale + this.viewOffset.x) / dpr + rect.left;
                    const screenY = (node.y * this.scale + this.viewOffset.y) / dpr + rect.top;

                    const width = node.width;
                    const height = node.height;

                    this.input.style.left = `${screenX - width / 2}px`;
                    this.input.style.top = `${screenY - height / 2}px`;
                    this.input.style.width = `${width}px`;
                    this.input.style.height = `${height}px`;
                    this.input.style.maxHeight = '200px';
                    this.input.style.fontSize = '14px';
                    this.input.style.position = 'absolute';
                    this.input.style.display = 'block';
                    this.input.style.zIndex = '1000';
                    this.input.style.backgroundColor = 'white';
                    this.input.style.border = '1px solid #ccc';
                    this.input.style.borderRadius = '5px';
                    this.input.style.boxShadow = '0 2px 10px rgba(0, 0, 0, 0.1)';
                    this.input.style.padding = '8px';
                    this.input.style.wordWrap = 'break-word';
                    this.input.style.whiteSpace = 'pre-wrap';
                    this.input.style.overflow = 'auto';
                    this.input.style.resize = 'none';
                    this.input.style.lineHeight = '1.5';
                    this.input.style.fontFamily = '"Segoe UI", system-ui, sans-serif';

                    this.input.focus();

                    const finishTextEdit = () => {
                        node.text = this.input.value.trim() || node.text;
                        this.input.style.display = 'none';
                        this.input.removeEventListener('blur', finishTextEdit);
                        this.input.removeEventListener('keydown', handleTextKeydown);
                        this.render();
                    };

                    const handleTextKeydown = e => {
                        if (e.key === 'Enter') this.input.blur();
                    };

                    this.input.addEventListener('blur', finishTextEdit);
                    this.input.addEventListener('keydown', handleTextKeydown);
                }
            }
        });

        this.canvas.addEventListener('wheel', e => {
            e.preventDefault();
            const { x, y } = this._getMouse(e);
            const scaleAmount = e.deltaY > 0 ? 0.9 : 1.1;
            this.viewOffset.x = (this.viewOffset.x - x) * scaleAmount + x;
            this.viewOffset.y = (this.viewOffset.y - y) * scaleAmount + y;
            this.scale *= scaleAmount;
            this._drawOnly();
        });
    }

    _getMouse(e, includeClient = false) {
        const rect = this.canvas.getBoundingClientRect();
        const dpr = window.devicePixelRatio || 1;

        const canvasX = e.clientX - rect.left;
        const canvasY = e.clientY - rect.top;

        const x = (canvasX * dpr - this.viewOffset.x) / this.scale;
        const y = (canvasY * dpr - this.viewOffset.y) / this.scale;

        const pos = { x, y };
        if (includeClient) {
            pos.clientX = e.clientX;
            pos.clientY = e.clientY;
        }
        return pos;
    }

    _getNodeAt(x, y) {
        if (!this.root) return null;

        const nodes = this._flatten(this.root);
        if (!nodes || nodes.length === 0) return null;

        return nodes.find(n => {
            if (!n || !n.text) return false;

            const width = n.width;
            const height = n.height;

            const tolerance = 10;
            let tagAreaWidth = 0;
            if (n.tags && n.tags.length > 0) {
                this.ctx.font = '12px "Segoe UI", system-ui, sans-serif';
                const maxTagWidth = Math.max(...n.tags.map(tag => this.ctx.measureText(tag).width));
                tagAreaWidth = maxTagWidth + 50;
            }

            const isInMainNode = (
                x >= n.x - width / 2 - tolerance &&
                x <= n.x + width / 2 + tolerance + tagAreaWidth &&
                y >= n.y - height / 2 - tolerance &&
                y <= n.y + height / 2 + tolerance
            );

            const buttonRadius = 10;
            const buttonX = n.x + width / 2 + buttonRadius + 5;
            const buttonY = n.y;
            const isInButton = n.children.length > 0 && (
                Math.pow(x - buttonX, 2) + Math.pow(y - buttonY, 2) <= Math.pow(buttonRadius + tolerance, 2)
            );

            return isInMainNode || isInButton;
        });
    }

    _layoutTree(node, depth = 0, nextY = { value: 0 }, parent = null, allNodes = []) {
        const baseNodeGap = 60; // 垂直间距
        const maxWidth = 200;
        const padding = 12;
        const lineHeight = 18;
        const tagPadding = 12;
        const levelGap = 250; // 增加水平层级间距
        const minLevelGap = 220; // 增加最小水平间距
        const minVerticalGap = 40; // 最小垂直间距

        // Calculate node dimensions
        const maxTextWidth = maxWidth - padding * 2;
        const textLines = this._wrapText(node.text, maxTextWidth);

        this.ctx.font = '14px "Segoe UI", system-ui, sans-serif';
        let maxLineWidth = 0;
        textLines.forEach(line => {
            const lineWidth = this.ctx.measureText(line).width;
            maxLineWidth = Math.max(maxLineWidth, lineWidth);
        });

        let tagAreaWidth = 0;
        if (node.tags && node.tags.length > 0) {
            this.ctx.font = '12px "Segoe UI", system-ui, sans-serif';
            const maxTagWidth = Math.max(...node.tags.map(tag => this.ctx.measureText(tag).width));
            tagAreaWidth = maxTagWidth + tagPadding * 2;
        }

        const textHeight = textLines.length * lineHeight;
        const height = Math.max(textHeight + padding * 2, node.tags && node.tags.length > 0 ? node.tags.length * 16 + padding * 2 : 0);
        const width = Math.max(maxLineWidth + padding * 2, 80) + tagAreaWidth;

        node.textLines = textLines;
        node.height = height;
        node.width = width;

        // 设置水平位置，确保从左到右排列
        const baseX = 100;
        const horizontalLineLength = 50;
        // 根据深度计算x坐标，保证每一层的节点都在同一垂直线上
        node.x = baseX + (depth * (levelGap + horizontalLineLength));

        // Initialize vertical position
        node.y = nextY.value * baseNodeGap + 100;

        allNodes.push(node);

        if (node.children && node.children.length > 0 && !node.collapsed) {
            // Calculate subtree height for each child to ensure symmetry
            const childHeights = node.children.map(child => {
                const tempNextY = { value: nextY.value };
                this._layoutTree(child, depth + 1, tempNextY, node, allNodes);
                return tempNextY.value - nextY.value; // Height of subtree
            });

            // Calculate total height and vertical center position
            const totalSubtreeHeight = childHeights.reduce((sum, h) => sum + h, 0) * baseNodeGap;
            const centerY = node.y; // Use parent's position as center reference
            let currentY = centerY - totalSubtreeHeight / 2; // Start from top of centered distribution

            // First pass: Position children symmetrically
            node.children.forEach((child, i) => {
                // Calculate ideal position for perfect symmetry
                const idealY = currentY + (childHeights[i] * baseNodeGap) / 2;
                child.y = idealY;
                currentY += childHeights[i] * baseNodeGap;
            });

            // Second pass: Adjust for minimum gaps and maintain symmetry
            for (let i = 0; i < node.children.length; i++) {
                const child = node.children[i];
                if (i > 0) {
                    const prevChild = node.children[i - 1];
                    const gap = child.y - prevChild.y;
                    const minGap = minVerticalGap + (prevChild.height + child.height) / 2;
                    
                    if (gap < minGap) {
                        // Calculate shift needed
                        const shift = (minGap - gap) / 2;
                        
                        // Move current child down and previous child up to maintain symmetry
                        child.y += shift;
                        prevChild.y -= shift;
                        
                        // Propagate changes to maintain relative positions
                        for (let j = i + 1; j < node.children.length; j++) {
                            node.children[j].y += shift * 2;
                        }
                        for (let j = i - 2; j >= 0; j--) {
                            node.children[j].y -= shift * 2;
                        }
                    }
                }
            }

            // Update nextY based on the last child's position
            nextY.value = (node.children[node.children.length - 1].y - 100) / baseNodeGap + childHeights[childHeights.length - 1] / 2 + 1;

            // Center parent vertically among children
            const firstChildY = node.children[0].y;
            const lastChildY = node.children[node.children.length - 1].y;
            node.y = (firstChildY + lastChildY) / 2;

            // 确保所有子节点在同一垂直线上
            const childX = node.x + levelGap;
            node.children.forEach(child => {
                child.x = childX;
            });

            // Collision detection with other nodes
            const currentBranchNodes = node.children;
            const otherBranchNodes = allNodes.filter(n => !currentBranchNodes.includes(n) && n !== node);

            let parentSiblings = [];
            if (parent) {
                const grandParent = this._findParent(this.root, parent);
                if (grandParent) {
                    parentSiblings = grandParent.children.filter(n => n !== parent);
                }
            }

            for (const currNode of currentBranchNodes) {
                for (const otherNode of otherBranchNodes) {
                    if (Math.abs(currNode.x - otherNode.x) < minLevelGap) {
                        const verticalGap = Math.abs(currNode.y - otherNode.y);
                        const requiredGap = (currNode.height + otherNode.height) / 2 + minVerticalGap;

                        if (verticalGap < requiredGap) {
                            const shift = requiredGap - verticalGap;
                            if (currNode.y > otherNode.y) {
                                currNode.y += shift;
                            } else {
                                currNode.y -= shift;
                            }
                            nextY.value = Math.max(nextY.value, (currNode.y - 100) / baseNodeGap + 1);
                        }
                    }
                }

                for (const parentSibling of parentSiblings) {
                    const verticalGap = Math.abs(currNode.y - parentSibling.y);
                    const requiredGap = (currNode.height + parentSibling.height) / 2 + minVerticalGap;

                    if (verticalGap < requiredGap) {
                        const shift = requiredGap - verticalGap;
                        if (currNode.y > parentSibling.y) {
                            currNode.y += shift;
                        } else {
                            currNode.y -= shift;
                        }
                        nextY.value = Math.max(nextY.value, (currNode.y - 100) / baseNodeGap + 1);
                    }
                }
            }

            // Re-center parent after collision adjustments
            const updatedFirstChildY = node.children[0].y;
            const updatedLastChildY = node.children[node.children.length - 1].y;
            node.y = (updatedFirstChildY + updatedLastChildY) / 2;
        } else {
            nextY.value++;
        }
    }

    _flatten(node) {
        let result = [node];
        if (node.children && !node.collapsed) {
            node.children.forEach(child => {
                result = result.concat(this._flatten(child));
            });
        }
        return result;
    }

    _drawLine(from, to) {
        const x1 = from.x + from.width / 2;
        const y1 = from.y;
        const x2 = to.x - to.width / 2;
        const y2 = to.y;

        const controlPointOffset = 30;
        const cp1x = x1 + controlPointOffset;
        const cp2x = x2 - controlPointOffset;

        this.ctx.beginPath();
        this.ctx.moveTo(x1, y1);
        this.ctx.bezierCurveTo(cp1x, y1, cp2x, y2, x2, y2);

        this.ctx.strokeStyle = '#666';
        this.ctx.lineWidth = 1.5;
        this.ctx.lineJoin = 'round';
        this.ctx.lineCap = 'round';
        this.ctx.stroke();
    }

    _wrapText(text, maxWidth) {
        const lines = [];
        let line = '';
        this.ctx.font = '14px "Segoe UI", system-ui, sans-serif';

        const chars = Array.from(text);
        
        for (let i = 0; i < chars.length; i++) {
            const char = chars[i];
            const testLine = line + char;
            const { width } = this.ctx.measureText(testLine);

            if (width > maxWidth && line !== '') {
                lines.push(line);
                line = char;
            } else {
                line = testLine;
            }

            if (char === ' ' && i < chars.length - 1) {
                let nextWord = '';
                let j = i + 1;
                while (j < chars.length && chars[j] !== ' ' && /[a-zA-Z]/.test(chars[j])) {
                    nextWord += chars[j];
                    j++;
                }
                
                if (nextWord) {
                    const testWithNextWord = line + nextWord;
                    const nextWidth = this.ctx.measureText(testWithNextWord).width;
                    if (nextWidth > maxWidth) {
                        lines.push(line);
                        line = '';
                    }
                }
            }
        }

        if (line) lines.push(line);
        return lines;
    }

    _drawNode(node) {
        const { x, y, width, height, textLines } = node;
        const radius = 12;

        const getNodeDepth = (node) => {
            let depth = 0;
            let current = node;
            let parent = this._findParent(this.root, current);
            while (parent) {
                depth++;
                current = parent;
                parent = this._findParent(this.root, current);
            }
            return depth;
        };

        const depth = getNodeDepth(node);

        const colorSchemes = [
            { fill: ['#f3e5f5', '#e1bee7'], stroke: '#8e24aa', text: '#4a148c' },
            { fill: ['#e3f2fd', '#bbdefb'], stroke: '#1976d2', text: '#0d47a1' },
            { fill: ['#e8f5e9', '#c8e6c9'], stroke: '#388e3c', text: '#1b5e20' },
            { fill: ['#fff3e0', '#ffe0b2'], stroke: '#f57c00', text: '#e65100' },
            { fill: ['#fbe9e7', '#ffccbc'], stroke: '#d84315', text: '#bf360c' }
        ];

        const scheme = colorSchemes[Math.min(depth, colorSchemes.length - 1)];

        this.ctx.save();
        this.ctx.shadowColor = 'rgba(0, 0, 0, 0.15)';
        this.ctx.shadowBlur = 10;
        this.ctx.shadowOffsetX = 2;
        this.ctx.shadowOffsetY = 2;

        this.ctx.beginPath();
        this._roundedRect(this.ctx, x - width / 2, y - height / 2, width, height, radius);

        if (node === this.activeNode) {
            this.ctx.lineWidth = 3;
        } else {
            this.ctx.lineWidth = 2;
        }

        const gradient = this.ctx.createLinearGradient(x - width / 2, y - height / 2, x + width / 2, y + height / 2);
        gradient.addColorStop(0, scheme.fill[0]);
        gradient.addColorStop(1, scheme.fill[1]);
        this.ctx.fillStyle = gradient;
        this.ctx.strokeStyle = scheme.stroke;

        this.ctx.fill();
        this.ctx.stroke();
        this.ctx.restore();

        const lineHeight = 18;
        const padding = 15;
        
        this.ctx.font = '14px "Segoe UI", system-ui, sans-serif';
        this.ctx.textAlign = 'left';
        this.ctx.textBaseline = 'middle';
        this.ctx.fillStyle = scheme.text;

        const textStartX = x - width / 2 + padding;
        const textStartY = y - (textLines.length * lineHeight) / 2;

        textLines.forEach((line, i) => {
            this.ctx.fillText(line, textStartX, textStartY + i * lineHeight + lineHeight / 2);
        });

        if (node.tags && node.tags.length > 0) {
            const tagHeight = 20;
            const tagSpacing = 5;
            const tagPadding = 8;

            this.ctx.font = '12px "Segoe UI", system-ui, sans-serif';
            
            const maxTextWidth = Math.max(...textLines.map(line => this.ctx.measureText(line).width));
            let currentX = textStartX + maxTextWidth + padding;

            node.tags.forEach((tag, index) => {
                const tagWidth = this.ctx.measureText(tag).width + tagPadding * 2;

                this.ctx.fillStyle = 'rgba(0, 0, 0, 0.08)';
                this.ctx.beginPath();
                this._roundedRect(
                    this.ctx,
                    currentX,
                    y - tagHeight / 2,
                    tagWidth,
                    tagHeight,
                    tagHeight / 2
                );
                this.ctx.fill();

                this.ctx.fillStyle = scheme.text;
                this.ctx.textAlign = 'left';
                this.ctx.fillText(tag, currentX + tagPadding, y);

                currentX += tagWidth + tagSpacing;
            });
        }

        if (node.children.length > 0) {
            const buttonRadius = 8;
            const buttonX = x + width / 2 + buttonRadius + 2;
            const buttonY = y;

            this.ctx.beginPath();
            this.ctx.arc(buttonX, buttonY, buttonRadius, 0, 2 * Math.PI);
            this.ctx.fillStyle = '#ffffff';
            this.ctx.strokeStyle = scheme.stroke;
            this.ctx.lineWidth = 1;
            this.ctx.fill();
            this.ctx.stroke();

            this.ctx.fillStyle = scheme.text;
            this.ctx.font = '12px "Segoe UI", system-ui, sans-serif';
            this.ctx.textAlign = 'center';
            this.ctx.textBaseline = 'middle';
            this.ctx.fillText(node.collapsed ? '+' : '-', buttonX, buttonY);
        }
    }

    _measureText(text) {
        this.ctx.font = '14px "Segoe UI", system-ui, sans-serif';
        return this.ctx.measureText(text);
    }

    _roundedRect(ctx, x, y, width, height, radius) {
        ctx.moveTo(x + radius, y);
        ctx.lineTo(x + width - radius, y);
        ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
        ctx.lineTo(x + width, y + height - radius);
        ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
        ctx.lineTo(x + radius, y + height);
        ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
        ctx.lineTo(x, y + radius);
        ctx.quadraticCurveTo(x, y, x + radius, y);
        ctx.closePath();
    }

    _drawOnly() {
        this.ctx.setTransform(1, 0, 0, 1, 0, 0);
        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
        this.ctx.setTransform(this.scale, 0, 0, this.scale, this.viewOffset.x, this.viewOffset.y);

        const nodes = this._flatten(this.root);
        this._drawLinks(this.root);
        nodes.forEach(n => this._drawNode(n));
    }

    _drawLinks(node) {
        if (!node.collapsed) {
            node.children.forEach(child => {
                this._drawLine(node, child);
                this._drawLinks(child);
            });
        }
    }

    render() {
        this._layoutTree(this.root);
        this._drawOnly();
    }

    _findParent(root, targetNode) {
        if (!root || !root.children) return null;

        if (root.children.includes(targetNode)) {
            return root;
        }

        for (const child of root.children) {
            if (!root.collapsed) {
                const parent = this._findParent(child, targetNode);
                if (parent) return parent;
            }
        }

        return null;
    }
}